package dataloader

import (
	"context"
	"fmt"
	"strings"

	"mall/common/enumx"
	"mall/service/layout/rpc/layout"
	"mall/service/wpgql/api/internal/graph/model"
	"mall/service/wpgql/api/internal/graph/storage"
	"mall/service/wpgql/api/internal/svc"

	"github.com/graph-gophers/dataloader"
	gopher_dataloader "github.com/graph-gophers/dataloader"
)

// GetMenuItem wraps the MenuItem dataloader for efficient retrieval by menuItem ID
func GetMenuItem(ctx context.Context, menuItemID string) (*model.MenuItem, error) {
	loaders := For(ctx)
	if loaders == nil {
		return nil, nil
	}

	thunk := loaders.menuItemLoader.Load(ctx, gopher_dataloader.StringKey(menuItemID))
	result, err := thunk()
	if err != nil {
		return nil, err
	}

	output, ok := result.(*model.MenuItem)
	if !ok {
		return nil, nil
	}

	return output, nil
}

type MenuItemBatcher struct {
	storage storage.Storage
	svcCtx  *svc.ServiceContext
}

func NewMenuItemBatcher(svcCtx *svc.ServiceContext) *MenuItemBatcher {
	return &MenuItemBatcher{
		storage: svcCtx.Storage,
		svcCtx:  svcCtx,
	}
}

func (b *MenuItemBatcher) get(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
	fmt.Printf("dataloader.MenuItemBatcher.get, menuItems: [%s]\n", strings.Join(keys.Keys(), ","))
	// create a map for remembering the order of keys passed in
	keyOrder := make(map[string]int, len(keys))
	// collect the keys to search for
	var dbIDs []string
	for idx, key := range keys {
		dbIDs = append(dbIDs, key.String())
		keyOrder[key.String()] = idx
	}

	// search for those categories
	dbRecords, err := b.storage.GetMenuItems(ctx, dbIDs)
	// if DB error, return
	if err != nil {
		return []*dataloader.Result{{Data: nil, Error: err}}
	}

	// construct an output array of dataloader results
	results := make([]*dataloader.Result, len(keys))
	// enumerate records, put into output
	for _, record := range dbRecords {
		if record.DatabaseID > 0 { // 缓存有记录，数据库有记录
			idx, ok := keyOrder[record.ID]
			// if found, remove from index lookup map so we know elements were found
			if ok {
				results[idx] = &dataloader.Result{Data: record, Error: nil}
				delete(keyOrder, record.ID)
			}
		} else { // 缓存有记录，数据库无记录
			idx, ok := keyOrder[record.ID]
			if ok {
				e := fmt.Errorf("menuItem not found %s", record.ID)
				results[idx] = &dataloader.Result{Data: nil, Error: e}
				delete(keyOrder, record.ID)
			}
		}
	}

	var ids []string // 缓存无记录
	for dbID, _ := range keyOrder {
		id, err := model.DecodeID("navMenu", dbID, b.svcCtx.Config.Query.CursorSplitter)
		if err == nil {
			ids = append(ids, fmt.Sprintf("%v", id))
		}
	}
	if len(ids) > 0 {
		res, err := b.svcCtx.LayoutRpc.MenuItemList(ctx, &layout.MenuItemListRequest{
			PostStatus: int32(enumx.PostStatusPublish),
			Field:      "Id",
			Value:      ids,
		})
		if err != nil || len(res.Data) == 0 { // 查询失败或无数据，向缓存填充空数据，防止反复查询
			for dbID, idx := range keyOrder {
				er := b.storage.PutMenuItem(ctx, &model.MenuItem{
					DatabaseID: 0,
					ID:         dbID,
				})
				if er != nil {
					results[idx] = &dataloader.Result{Data: nil, Error: er}
				} else {
					results[idx] = &dataloader.Result{Data: nil, Error: err}
				}
				delete(keyOrder, dbID)
			}
		} else {
			for _, menuItem := range res.Data {
				var id string
				id = model.EncodeID("navMenu", menuItem.Id, b.svcCtx.Config.Query.CursorSplitter)
				var parentId string
				if menuItem.ParentId > 0 {
					parentId = model.EncodeID("navMenu", menuItem.ParentId, b.svcCtx.Config.Query.CursorSplitter)
				}
				var classes []*string
				if len(menuItem.Classes) > 0 {
					for _, class := range menuItem.Classes {
						classes = append(classes, &class)
					}
				}
				var (
					order            int = int(menuItem.Order)
					parentDatabaseID int = int(menuItem.ParentId)
				)
				record := &model.MenuItem{
					CSSClasses:       classes,
					DatabaseID:       int(menuItem.Id),
					ID:               id,
					Label:            &menuItem.Title,
					Order:            &order,
					ParentDatabaseID: &parentDatabaseID,
					ParentID:         &parentId,
					URL:              &menuItem.Link,
					Target:           &menuItem.Target,
					Title:            &menuItem.Title,
				}
				b.storage.PutMenuItem(ctx, record)

				idx, ok := keyOrder[id]
				// if found, remove from index lookup map so we know elements were found
				if ok {
					results[idx] = &dataloader.Result{Data: record, Error: nil}
					delete(keyOrder, id)
				}
			}
		}
	}

	// fill array positions with errors where not found in DB
	for dbID, idx := range keyOrder {
		err := fmt.Errorf("category not found %s", dbID)
		results[idx] = &dataloader.Result{Data: nil, Error: err}
	}

	// return results
	return results
}
